#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/spinlock.h>

#include "log.h"

#define ERROR         (-1)
#define MINORS        (64)
#define MAX_4K_CNT 	  (4)
#define DISK_4K_LSECTORS  (1024)

struct block_tmp_device
{
	struct request_queue *queue;
	struct gendisk       *disk;
	int8_t blk_name[20];
	int    major;
	spinlock_t data_lock;
	
	int32_t lba_min;
	int32_t lba_max;

	uint8_t buffers[DISK_4K_LSECTORS*4096];
};

struct block_tmp_device blk_dev;

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10,0)
static int block_tmp_bio_request(struct request_queue *q, struct bio *bio)
#else
static void  block_tmp_bio_request(struct request_queue *q, struct bio *bio)
#endif
{
	static uint32_t refcount = 0;
	uint32_t i = 0, count = 0;
	uint8_t  write = bio_data_dir(bio);
	struct bio_vec *bvec, *bvprv = NULL;
	uint8_t *buf, *data;
	struct block_tmp_device *dev = q->queuedata;

	refcount++;
	//schedule_timeout_interruptible(500);
	//while (i++ < 100000);
	bio_for_each_segment(bvec, bio, i)
	{
		count++;
		log(DBG_LOG, "bio %p page %p offset %lu len %lu count %lu\r\n",bio, bvec->bv_page, bvec->bv_offset, bvec->bv_len, count);
	}
	bvec = &bio->bi_io_vec[bio->bi_idx];

	buf = dev->buffers + (bio->bi_sector << 9) + bvec->bv_offset; 
	data = page_address(bvec->bv_page) + bvec->bv_offset;

#if 1
	//lock
	spin_lock(&dev->data_lock);
	if(write)
		memcpy(buf, data, bvec->bv_len);
	else
		memcpy(data, buf, bvec->bv_len);
	bio_endio(bio, 0);
	spin_unlock(&dev->data_lock);
	//unlock
#endif

	refcount--;
	if(refcount != 0)
	    ;//log(DBG_LOG, "%s refcount %d \r\n", __FUNCTION__, refcount);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10,0)
	return 0;
#endif
}
static int block_tmp_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		default:
			return 0;
	}

	return 0;
}

static const struct block_device_operations block_tmp_fops = {
	.owner = THIS_MODULE,
	.ioctl = block_tmp_ioctl,
	.compat_ioctl = block_tmp_ioctl,
};

static int __init block_tmp_init(void)
{
	int result;

	blk_dev.major = 0;

	printk("module 0 init\r\n");
	sprintf(blk_dev.blk_name, "s5bd0");
	result = register_blkdev(blk_dev.major, blk_dev.blk_name);	
	if(result < 0)
	{
		log(DBG_ERROR, "%s %d \r\n", __FUNCTION__, __LINE__);
		return ERROR;
	}
	else if(result > 0)
		blk_dev.major = result;

	log(DBG_LOG, "blk_dev major %d name %s result %d\r\n", blk_dev.major, blk_dev.blk_name, result);
	blk_dev.queue = blk_alloc_queue(GFP_KERNEL);
	blk_dev.queue->queue_flags = QUEUE_FLAG_DEFAULT;
	queue_flag_set_unlocked(QUEUE_FLAG_NOMERGES, blk_dev.queue);
	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, blk_dev.queue);
	blk_queue_make_request(blk_dev.queue, block_tmp_bio_request);
	blk_dev.queue->queuedata = &blk_dev;

		blk_dev.disk = alloc_disk(MINORS);
		blk_queue_logical_block_size(blk_dev.queue, 4096);
		blk_queue_max_hw_sectors(blk_dev.queue, 8*MAX_4K_CNT);

		blk_dev.disk->major = blk_dev.major;
		blk_dev.disk->minors = MINORS;
		blk_dev.disk->first_minor = 0;
		blk_dev.disk->fops = &block_tmp_fops;
		blk_dev.disk->private_data = &blk_dev;
		blk_dev.disk->queue = blk_dev.queue;
		blk_dev.lba_min = 0xFFFFFFFF;
		blk_dev.lba_max = 0;
	sprintf(blk_dev.disk->disk_name, "s5bd0");
	set_capacity(blk_dev.disk, (uint64_t)DISK_4K_LSECTORS *8);
	add_disk(blk_dev.disk);

	printk("module 0 init end\r\n");
	return 0;
}

static void __exit block_tmp_exit(void)
{
	printk("module 0 exit\r\n");

	blk_cleanup_queue(blk_dev.queue);
	if (blk_dev.disk->flags & GENHD_FL_UP)
		del_gendisk(blk_dev.disk);
	unregister_blkdev(blk_dev.major, blk_dev.blk_name);

	schedule_timeout(HZ/2);
	printk("module 0 exit end\r\n");
}

module_init(block_tmp_init);
module_exit(block_tmp_exit);
MODULE_LICENSE("GPL");
